using System;
using System.Text;
using System.Text.RegularExpressions;

namespace PopMail
{
    public class POP3MimeParser
    {
        private POP3MailMessage processedMessage;
        private string[] rawMessageLines;
        private int startOfBody;
        private int endOfMessage;

        public POP3MimeParser(StringBuilder rawMessage,long mailBoxPosition)
        {
            this.processedMessage = new POP3MailMessage();
            startOfBody = 0;

            if (rawMessage == null)
            {
                // Ideally we should have received something but if it is null something is wrong. 
                // Throw exception accordingly

                processedMessage = null;
            }

            processedMessage.RawMessage = rawMessage.ToString();

            //Now iterate through the message lines to retrieve various message elements
            rawMessageLines = rawMessage.ToString().Split(new char[] { '\r' });
            endOfMessage = rawMessageLines.Length;

            ProcessMIMEHeaders(mailBoxPosition);
            //SetMessageSignature();
            ProcessMIMEBodyAndAttachments();

        }

        public bool ProcessMIMEHeaders(long mailBoxPosition)
        {
            bool endOfHeaderProcessing = false;
            string dirtyContentType = string.Empty;

            // Set the message number of the mail in context
            processedMessage.InboxPosition = mailBoxPosition;

            for (int i = 0; i < rawMessageLines.Length; i++)
            {
                string currentLine = rawMessageLines[i].Replace("\n", "");

                if (processedMessage.IsHeaderRetrievalComplete())
                {
                    if (processedMessage.IsMultiPart && currentLine.IndexOf(processedMessage.Boundary) != -1)
                    {
                        endOfHeaderProcessing = true;
                        startOfBody = i - 1;
                        break;
                    }

                    if (!processedMessage.IsMultiPart && currentLine.Equals(""))
                    {
                        endOfHeaderProcessing = true;
                        startOfBody = i + 1;
                        break;
                    }
                }
                if (i == 0) //First line usually contains +OK <size of message> Octets
                {
                    processedMessage.MessageSize = long.Parse(currentLine.Split(' ')[1].ToString());
                }

                if (POP3Constants.FromPattern.IsMatch(currentLine))
                {
                    processedMessage.From = StringClean(
                    Regex.Replace(currentLine, @"^From:.*[ |<]([a-z|A-Z|0-9|\.|\-|_]+@[a-z|A-Z|0-9|\.|\-|_]+).*$", "$1"));
                }

                if (POP3Constants.ToPattern.IsMatch(currentLine))
                {
                    processedMessage.To = StringClean(
                    Regex.Replace(currentLine, @"^To:.*[ |<]([a-z|A-Z|0-9|\.|\-|_]+@[a-z|A-Z|0-9|\.|\-|_]+).*$", "$1"));
                }

                if (POP3Constants.SubjectPattern.IsMatch(currentLine))
                {
                    processedMessage.Subject = StringClean(Regex.Replace(currentLine, @"^Subject: (.*)$", "$1"));
                }

                if (POP3Constants.DatePattern.IsMatch(currentLine))
                {
                    string dirtyDate = Regex.Replace(currentLine, @"^Date: (.*)$", "$1");

                    if (dirtyDate.IndexOf("(") != -1 && dirtyDate.IndexOf(")") != -1)
                    {
                        //encountered something like (PDT) at the end of date time string. So we need to chop it off
                        dirtyDate = dirtyDate.Split('(')[0];
                    }

                    dirtyDate = dirtyDate.Split('+')[0];
                    processedMessage.MailReceivedDate = DateTime.Parse(StringClean(dirtyDate));
                    
                    dirtyDate = null;
                }

                if (POP3Constants.ContentTypePattern.IsMatch(currentLine))
                {
                    //processedMessage.ContentType = StringClean(Regex.Replace(currentLine, @"^Content-Type: (.*)$", "$1"));

                    if (currentLine.IndexOf(";") != -1)
                    {
                        // Encountered something like Content-Type: text/plain; charset=ISO-8859-1
                        // So we need to process it accordingly
                        dirtyContentType = Regex.Replace(currentLine, @"^Content-Type: (.*)$", "$1");
                        processedMessage.ContentType = StringClean(dirtyContentType.Split(';')[0]);
                    }
                    else
                    {
                        processedMessage.ContentType = StringClean(Regex.Replace(currentLine, @"^Content-Type: (.*)$", "$1"));
                    }


                    if (currentLine.IndexOf("multipart") != -1)
                    {
                        processedMessage.IsMultiPart = true;
                    }

                    if (currentLine.IndexOf("text/plain") != -1)
                    {
                        processedMessage.IsMultiPart = false;
                    }

                }

                if (currentLine.IndexOf("boundary=") != -1)
                {
                    processedMessage.Boundary = StringClean(Regex.Replace(currentLine, @"^(.*)boundary=(.*)$", "$2"));
                }

                if (currentLine.IndexOf("charset") != -1)
                {
                    processedMessage.Charset = StringClean(Regex.Replace(currentLine, @"^(.*)charset=(.*)$", "$2"));
                }

                if (POP3Constants.ContentTransferEncodingPattern.IsMatch(currentLine))
                {
                    processedMessage.Encoding = StringClean(Regex.Replace(currentLine, @"^Content-Transfer-Encoding:(.*)$", "$1"));
                }

            }

            dirtyContentType = null;

            return endOfHeaderProcessing;

        }

        public bool ProcessMIMEBodyAndAttachments()
        {
            bool processedStatus = false;
            StringBuilder bodyText = new StringBuilder();

            // If there are no attachments then mail body is straight forward extraction of message after headers.
            if (!processedMessage.IsMultiPart) 
            {
                for (int i = startOfBody; i < endOfMessage - 3 ; i++)
                {
                    bodyText.AppendLine(rawMessageLines[i].Replace("\n", ""));
                }

                string encodedMessage = StringClean(bodyText.ToString());

                if (processedMessage.Encoding.IndexOf("quoted-printable") != -1)
                {
                    processedMessage.Body = QuotedPrintable.Decode(encodedMessage);
                }
                else
                {
                    processedMessage.Body = encodedMessage;
                }

                bodyText = null;
                processedStatus = true;

            }
            else
            {
                POP3MailAttachment attachment = null;
                int attachmentNumber = 0;

                for (int i = startOfBody; i < endOfMessage; i++)
                {
                    string currentLine = rawMessageLines[i].Replace("\n", "");
                    string boundary = string.Empty;
                    
                    //End of message has been encountered
                    if (currentLine.IndexOf("--" + processedMessage.Boundary + "--") != -1)
                    {
                        break;
                    }

                    if (currentLine.IndexOf(processedMessage.Boundary) != -1
                        || POP3Constants.AttachmentSeperatorPattern.IsMatch(currentLine))
                    {
                        boundary = currentLine.Replace("\r", "").Replace("\n", "");
                        attachment = new POP3MailAttachment();
                        int startOfBoundary = i + 1; 
                       
                        attachment = ProcessMIMEAttachment(boundary, startOfBoundary);
                        attachment.StartOfBoundary = startOfBoundary;
                        attachment.MessageSignature = processedMessage.MessageSignature;

                        if (!attachment.IsPartiallyProcessed)
                        {
                            attachmentNumber++;

                            if (attachmentNumber == 1 && attachment.ContentType.IndexOf("text/plain") != -1)
                            {
                                processedMessage.Body = attachment.Stream;
                            }
                            else
                            {
                                processedMessage.AddAttachment(attachment);
                            }
                        }
 
                        i = attachment.EndOfBoundary;

                        attachment = null;
                        continue;
                    }
                }

                processedStatus = true;
            }

            return processedStatus;
        }

        public POP3MailAttachment ProcessMIMEAttachment(string attachmentBoundary,int startOfBoundary)
        {
            POP3MailAttachment attachment = new POP3MailAttachment();
            int attachmentStreamMarker = 0;
            string boundary = string.Empty;
            string dirtyContentType = string.Empty;
            string dirtyCharset = string.Empty;

            // Check withnin first 5 lines of the header if there are nested attachments. 
            // If yes - then recurse. Usually I would not expect more than first level of recursion

            for (int i = startOfBoundary; i < startOfBoundary + 5 ; i++)
            {
                string currentLine = rawMessageLines[i].Replace("\n", "");

                if (POP3Constants.ContentTypePattern.IsMatch(currentLine))
                {
                    attachment.ContentType = StringClean(Regex.Replace(currentLine, @"^Content-Type: (.*)$", "$1"));
                }

                if (currentLine.IndexOf("boundary=") != -1)
                {
                    boundary = StringClean(Regex.Replace(currentLine, @"^(.*)boundary=(.*)$", "$2"));
                    continue;
                }

                // A new boundary encountered means nested attachments.
                if (attachment.ContentType.IndexOf("multipart") != -1 
                    && !boundary.Equals(string.Empty)
                    && Regex.Equals(currentLine,"--"+ boundary))
                {
                    attachment.EndOfBoundary = i - 1;
                    attachment.IsPartiallyProcessed = true;
                    return attachment;
                }

            }

            for (int i = startOfBoundary; i < endOfMessage; i++)
            {
                string currentLine = rawMessageLines[i].Replace("\n", "");

                if (currentLine.Equals(""))
                {
                    attachmentStreamMarker = i;
                    break;
                }

                if (POP3Constants.ContentTypePattern.IsMatch(currentLine))
                {
                    if (currentLine.IndexOf("charset") != -1 && currentLine.IndexOf(";") != -1)
                    {
                        // Encountered something like Content-Type: text/plain; charset=ISO-8859-1
                        // So we need to process it accordingly
                        dirtyContentType = Regex.Replace(currentLine, @"^Content-Type: (.*)$", "$1");
                        attachment.ContentType = StringClean(dirtyContentType.Split(';')[0]);
                    }
                    else
                    {
                        attachment.ContentType = StringClean(Regex.Replace(currentLine, @"^Content-Type: (.*)$", "$1"));
                    }

                }

                if (currentLine.IndexOf("charset") != -1)
                {
                    attachment.Charset = StringClean(Regex.Replace(currentLine, @"^(.*)charset=(.*)$", "$2"));
                }

                if (currentLine.IndexOf("name") != -1)
                {
                    attachment.Name = StringClean(Regex.Replace(currentLine, @"^(.*)name=(.*)$", "$2"));
                }

                if (POP3Constants.ContentTransferEncodingPattern.IsMatch(currentLine))
                {
                    attachment.Encoding = StringClean(Regex.Replace(currentLine, @"^Content-Transfer-Encoding:(.*)$", "$1"));
                }

            }

            StringBuilder stream = new StringBuilder();

            for (int j = attachmentStreamMarker; j < endOfMessage; j++)
            {
                if (rawMessageLines[j].Replace("\n", "").Equals(attachmentBoundary))
                {
                    attachment.EndOfBoundary = j - 1;
                    break;
                }

                if (rawMessageLines[j].Replace("\n", "").Equals(attachmentBoundary + "--"))
                {
                    attachment.EndOfBoundary = j;
                    break;
                }

                stream.AppendLine(rawMessageLines[j].Replace("\n", ""));
     
            }
            string encodedMessage = StringClean(stream.ToString());
            stream = null;

            if (attachment.Encoding.IndexOf("quoted-printable") != -1)
            {
                attachment.Stream = QuotedPrintable.Decode(encodedMessage);
            }
            else
            {
                attachment.Stream = encodedMessage;
            }

            if (dirtyContentType != string.Empty)
            {
                dirtyCharset = dirtyContentType.Split(';')[1].Split('=')[1];//should get something like  charset="iso-8859-1"
                dirtyCharset = dirtyCharset.Replace('\"', ' ');
                attachment.Charset = StringClean(dirtyCharset.ToLower());
            }

            dirtyContentType = null;
            dirtyCharset = null;

            return attachment;
        }

        public string StringClean(string toClean)
        {
            if (toClean == null)
                return toClean; //Log message that unwanted messages are getting in here


            toClean = toClean.Trim();
            toClean = toClean.TrimStart(POP3Constants.charsToTrimOne.ToCharArray());
            toClean = toClean.TrimEnd(POP3Constants.charsToTrimOne.ToCharArray());
            toClean = toClean.TrimEnd(POP3Constants.charsToTrimTwo.ToCharArray());
            toClean = toClean.TrimStart(POP3Constants.charsToTrimFour.ToCharArray());
            toClean = toClean.TrimEnd(POP3Constants.charsToTrimFour.ToCharArray());
            //toClean = toClean.TrimStart(POP3ConstantValues.charsToTrimThree.ToCharArray());

            return toClean;
        }

        //private void SetMessageSignature()
        //{
        //    StringBuilder input = new StringBuilder();

        //    input.Append(this.processedMessage.From);
        //    input.Append(this.processedMessage.To);
        //    input.Append(this.processedMessage.MailReceivedDate.ToString());
        //    input.Append(this.processedMessage.MessageSize);

        //    this.processedMessage.MessageSignature = CryptographicUtilityLibrary.getMd5Hash(input.ToString());

        //}

        public POP3MailMessage ProcessedMessage
        {
            get { return processedMessage; }
        }

    }

}
